﻿using System;
using System.IO;
using System.Security;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using System.ComponentModel;

namespace CafeteriaLib
{
	class CafeteriaLogger : Logger, IDisposable
	{
		// Pointer to an external unmanaged resource. 
		private IntPtr handle;
		// Other managed resource this class uses. 
		private Component component = new Component();
		// Track whether Dispose has been called. 
		private bool disposed = false;


		// The class constructor. 
		public CafeteriaLogger(IntPtr handle)
		{
			this.handle = handle;
		}

		/// <summary> 
		/// Initialize is guaranteed to be called by MSBuild at the start of the build 
		/// before any events are raised. 
		/// </summary> 
		public override void Initialize(IEventSource eventSource)
		{
			// The name of the log file should be passed as the first item in the 
			// "parameters" specification in the /logger switch.  It is required
			// to pass a log file to this logger. Other loggers may have zero or more than  
			// one parameters. 
			if (null == Parameters)
			{
				throw new LoggerException("Log file was not set.");
			}
			string[] parameters = Parameters.Split(';');

			string logFile = parameters[0];
			if (String.IsNullOrEmpty(logFile))
			{
				throw new LoggerException("Log file was not set.");
			}

			if (parameters.Length > 1)
			{
				throw new LoggerException("Too many parameters passed.");
			}

			try
			{
				// Open the file 
				this.streamWriter = new StreamWriter(logFile);
			}
			catch (Exception ex)
			{
				if
				(
					ex is UnauthorizedAccessException
					|| ex is ArgumentNullException
					|| ex is PathTooLongException
					|| ex is DirectoryNotFoundException
					|| ex is NotSupportedException
					|| ex is ArgumentException
					|| ex is SecurityException
					|| ex is IOException
				)
				{
					throw new LoggerException("Failed to create log file: " + ex.Message);
				}
				else
				{
					// Unexpected failure 
					throw;
				}
			}

			// For brevity, we'll only register for certain event types. Loggers can also 
			// register to handle TargetStarted/Finished and other events.
			eventSource.ProjectStarted += new ProjectStartedEventHandler(eventSource_ProjectStarted);
			eventSource.TaskStarted += new TaskStartedEventHandler(eventSource_TaskStarted);
			eventSource.MessageRaised += new BuildMessageEventHandler(eventSource_MessageRaised);
			eventSource.WarningRaised += new BuildWarningEventHandler(eventSource_WarningRaised);
			eventSource.ErrorRaised += new BuildErrorEventHandler(eventSource_ErrorRaised);
			eventSource.ProjectFinished += new ProjectFinishedEventHandler(eventSource_ProjectFinished);
		}

		// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		protected virtual void Dispose(bool disposing)
		{
			// Check to see if Dispose has already been called. 
			if (!this.disposed)
			{
				// If disposing equals true, dispose all managed 
				// and unmanaged resources. 
				if (disposing)
				{
					// Dispose managed resources.
					component.Dispose();
				}

				// Call the appropriate methods to clean up 
				// unmanaged resources here. 
				// If disposing is false, 
				// only the following code is executed.
				CloseHandle(handle);
				handle = IntPtr.Zero;

				// Note disposing has been done.
				disposed = true;
			}
		}

		// Use interop to call the method necessary 
		// to clean up the unmanaged resource.
		[System.Runtime.InteropServices.DllImport("Kernel32")]
		private extern static Boolean CloseHandle(IntPtr handle);

		void eventSource_ErrorRaised(object sender, BuildErrorEventArgs e)
		{
			// BuildErrorEventArgs adds LineNumber, ColumnNumber, File, amongst other parameters 
			string line = String.Format(": ERROR {0}({1},{2}): ", e.File, e.LineNumber, e.ColumnNumber);
			WriteLineWithSenderAndMessage(line, e);
		}

		void eventSource_WarningRaised(object sender, BuildWarningEventArgs e)
		{
			// BuildWarningEventArgs adds LineNumber, ColumnNumber, File, amongst other parameters 
			string line = String.Format(": Warning {0}({1},{2}): ", e.File, e.LineNumber, e.ColumnNumber);
			WriteLineWithSenderAndMessage(line, e);
		}

		void eventSource_MessageRaised(object sender, BuildMessageEventArgs e)
		{
			// BuildMessageEventArgs adds Importance to BuildEventArgs 
			// Let's take account of the verbosity setting we've been passed in deciding whether to log the message 
			if ((e.Importance == MessageImportance.High && IsVerbosityAtLeast(LoggerVerbosity.Minimal))
				|| (e.Importance == MessageImportance.Normal && IsVerbosityAtLeast(LoggerVerbosity.Normal))
				|| (e.Importance == MessageImportance.Low && IsVerbosityAtLeast(LoggerVerbosity.Detailed))
				)
			{
				WriteLineWithSenderAndMessage(String.Empty, e);
			}
		}

		void eventSource_TaskStarted(object sender, TaskStartedEventArgs e)
		{
			// TaskStartedEventArgs adds ProjectFile, TaskFile, TaskName 
			// To keep this log clean, this logger will ignore these events.

		}

		void eventSource_ProjectStarted(object sender, ProjectStartedEventArgs e)
		{
			// ProjectStartedEventArgs adds ProjectFile, TargetNames 
			// Just the regular message string is good enough here, so just display that.
			WriteLine(String.Empty, e);
			indent++;
		}

		void eventSource_ProjectFinished(object sender, ProjectFinishedEventArgs e)
		{
			// The regular message string is good enough here too.
			indent--;
			WriteLine(String.Empty, e);
		}

		/// <summary> 
		/// Write a line to the log, adding the SenderName and Message 
		/// (these parameters are on all MSBuild event argument objects) 
		/// </summary> 
		private void WriteLineWithSenderAndMessage(string line, BuildEventArgs e)
		{
			if (0 == String.Compare(e.SenderName, "MSBuild", true /*ignore case*/))
			{
				// Well, if the sender name is MSBuild, let's leave it out for prettiness
				WriteLine(line, e);
			}
			else
			{
				WriteLine(String.Format("{0}: {1}", e.SenderName, line), e);
			}
		}

		/// <summary> 
		/// Just write a line to the log 
		/// </summary> 
		private void WriteLine(string line, BuildEventArgs e)
		{
			for (int i = indent; i > 0; i--)
			{
				streamWriter.Write("\t");
			}
			streamWriter.WriteLine(line + e.Message);
		}

		/// <summary> 
		/// Shutdown() is guaranteed to be called by MSBuild at the end of the build, after all  
		/// events have been raised. 
		/// </summary> 
		public override void Shutdown()
		{
			// Done logging, let go of the file
			streamWriter.Close();
		}

		private StreamWriter streamWriter;
		private int indent;

		~CafeteriaLogger()
		{
			// Do not re-create Dispose clean-up code here. 
			// Calling Dispose(false) is optimal in terms of 
			// readability and maintainability.
			Dispose(false);
		}
	}
}
